原文:shell 脚本多后台问题? - 林果皞的回答 - 知乎

basic

shell 脚本中用{}& 实现多个后台并行执行来提高速度,用wait等待后台执行完再执行后面的命令。

后台无论执行成功与否,只要允许完wait后面就会执行,那么如果后台执行报错如何终止脚本呢?

set -e 和 wait -n

modao@modao-hp:~$ cat wait.sh
{ echo 1; sleep 2; echo 2; zhihu; echo 3 &} & echo 'a'; wait; echo 'b'
modao@modao-hp:~$ bash wait.sh
a
1
2
wait.sh: line 1: zhihu: command not found
3
b

在脚本头添加 set -e ( 或执行 bash -e wait.sh 也行), 遭遇错误即提前退出, 不过仍然会继续执行 wait 之后的 echo 'b':

modao@modao-hp:~$ cat wait.sh
set -e
{ echo 1; sleep 2; echo 2; zhihu; echo 3 &} & echo 'a'; wait; echo 'b'
modao@modao-hp:~$ bash wait.sh
a
1
2
wait.sh: line 2: zhihu: command not found
b

wait 加上 -n 参数即可提前退出整个脚本:(-n 是 bash 4.3 新增的参数)

modao@modao-hp:~$ cat wait.sh
set -e
{ echo 1; sleep 2; echo 2; zhihu; echo 3 &} & echo 'a'; wait -n; echo 'b'
modao@modao-hp:~$ bash wait.sh
a
1
2
wait.sh: line 2: zhihu: command not found

more

管道、括号才会开子 shell

|| && 会封闭 set -e,使其无效

见条件语句就封闭 -e

from Bash Reference

Exit immediately if a pipeline (see Section 3.2.2 [Pipelines],page 8), which may consist of a single simple command (seeSection 3.2.1 [Simple Commands], page 8), a list (see Section 3.2.3[Lists], page ), or a compound command (see Section 3.2.4[Compound Commands], page 9) returns a non-zero status.

如果任何管道以非零(”错误“)退出状态结束,立即终止脚本

The shell does not exit if the command that fails is part of the command list immediately following a while or until keyword, part of the test in an if statement, part of any command executed in a && or || list except the command following the final && or ||, any command in a pipeline but the last, or if the command’s return status is being inverted with !.

shell不会退出,如果失败的命令是:

  • 紧随在whileuntil 关键字之后的命令列表的一部分
  • if语句中的测试的一部分
  • 在**&&||列表中的命令,除了最终的&&||**之后的命令,管道中的任何命令都不会使shell退出
  • 失败的命令的返回状态用**!**反转

If a compound command other than a subshell returns a non-zero status because a command failed while -e was being ignored, the shell does not exit.

如果除子Shell之外的复合命令由于在-e被忽略时命令失败而返回非零状态,则该Shell不会退出。

A trap on ERR, if set, is executed before the shell exits.

如果设置了ERR陷阱,则会在shell程序退出之前执行陷阱。

This option applies to the shell environment and each subshell environment separately (see Section 3.7.3 [Command Execution nvironment], page 39), and may cause subshells to exit before executing all the commands in the subshell.

If a compound command or shell function executes in a context where -e is being ignored, none of the commands executed within the compound command or function body will be affected by the -e setting, even if -e is set and a command returns a failure status.

If a compound command or shell function sets -e while executing in a context where -e is ignored, that setting will not have any effect until the compound command or the command containing the function call completes.

该选项分别适用于Shell环境和每个子Shell环境(请参见第3.7.3节[命令执行环境],第39页),并且可能导致子Shell在执行子Shell中的所有命令之前退出。

如果复合命令或shell函数在忽略-e的上下文中执行,则即使设置了-e且命令返回了故障状态,复合命令或函数体内执行的所有命令都不会受到-e设置的影响。

如果在忽略-e的上下文中执行时,复合命令或shell函数设置了-e,则该设置在复合命令或包含该函数调用的命令完成之前不会生效。

porblem

原文

-e选项意味着“如果任何管道以非零(”错误“)退出状态结束,立即终止脚本”。由于grep在没有找到任何匹配时返回退出状态1,即使没有真正的“错误”,也可能导致-e终止脚本。

如果你想保留-e选项,但也有一个grep命令可能有效地找不到匹配,你可以追加||。 :to the grep命令。这意味着“或者,如果grep命令返回非零退出状态,运行:(它什么也不做)”;所以净效果是禁用grep命令的-e。所以:

grep PATTERN FILE... || :

编辑添加:上面的方法丢弃每个错误:如果grep返回1,因为它发现没有匹配,这被忽略,但如果grep返回2,因为有一个错误,被忽略,如果grep不在路径中Bash返回127),被忽略 – 等等。所以,而不是:,最好使用一个命令来检查结果代码,如果它不是1,重新发出错误。例如:

grep PATTERN FILE || (( $? == 1 ))

但这破坏了退出状态;通常,当失败的命令使用-e终止Bash脚本时,脚本将返回命令的退出状态,但在上面的示例中,脚本将只返回1.如果(并且只有)我们关心它,我们可以修复它通过写这样:

grep PATTERN FILE || exit_code=$?
if (( exit_code > 1 )) ; then
	exit $exit_code
fi

(第一行c / o dsummersl的评论)。

在这一点上,最好创建一个shell函数来处理这个:

function grep_no_match_ok () {    
	local exit_code    
	grep "$@" || exit_code=$?    
	return $(( exit_code == 1 ? 0 : exit_code ))
}

(注意使用返回而不是退出;我们将让-e在适当时处理退出);这样,我们可以只写:

grep_no_match_ok PATTERN FILE     
# won't kill script if no matches are found

事实上,由于我们最有可能想在这个脚本中使用这个函数来处理grep的所有事件,我们实际上可以命名函数grep:

function grep () {
	local exit_code
    command grep "$@" || exit_code=$?
	return $(( exit_code == 1 ? 0 : exit_code ))
}
grep PATTERN FILE    
# won't kill script if no matches are found

(注意使用命令来绕过其自身体中的shell函数:我们希望该函数调用常规程序grep,而不是无限递归)。